﻿/*
 * madplay - MPEG audio decoder and player
 * Copyright (C) 2000-2004 Robert Leslie
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: audio.c,v 1.36 2004/01/23 09:41:31 rob Exp $
 */

# include "stdafx.h"

# include <string.h>
#include "../MPlayer-Plugins/libmad-0.15.1b/mad.h"


# include "audio.h"

#pragma warning (disable : 4244)

char const *audio_error;

static struct audio_dither left_dither, right_dither;

# if defined(_MSC_VER)
#  pragma warning(disable: 4550)  /* expression evaluates to a function which
                     is missing an argument list */
# endif

/*
 * NAME:    clip()
 * DESCRIPTION:    gather signal statistics while clipping
 */
static inline
void clip(mad_fixed_t *sample, struct audio_stats *stats) {
    enum {
        MIN                         = -MAD_F_ONE,
        MAX                         =  MAD_F_ONE - 1
    };

    if (*sample >= stats->peak_sample) {
        if (*sample > MAX) {
            ++stats->clipped_samples;
            if (*sample - MAX > stats->peak_clipping) {
                stats->peak_clipping = *sample - MAX;
            }

            *sample = MAX;
        }
        stats->peak_sample = *sample;
    } else if (*sample < -stats->peak_sample) {
        if (*sample < MIN) {
            ++stats->clipped_samples;
            if (MIN - *sample > stats->peak_clipping) {
                stats->peak_clipping = MIN - *sample;
            }

            *sample = MIN;
        }
        stats->peak_sample = -*sample;
    }
}

/*
 * NAME:    audio_linear_round()
 * DESCRIPTION:    generic linear sample quantize routine
 */
# if defined(_MSC_VER)
extern  /* needed to satisfy bizarre MSVC++ interaction with inline */
# endif
inline
signed long audio_linear_round(unsigned int bits, mad_fixed_t sample,
    struct audio_stats *stats) {
    /* round */
    sample += (1L << (MAD_F_FRACBITS - bits));

    /* clip */
    clip(&sample, stats);

    /* quantize and scale */
    return sample >> (MAD_F_FRACBITS + 1 - bits);
}

/*
 * NAME:    prng()
 * DESCRIPTION:    32-bit pseudo-random number generator
 */
static inline
unsigned long prng(unsigned long state) {
    return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL;
}

/*
 * NAME:    audio_linear_dither()
 * DESCRIPTION:    generic linear sample quantize and dither routine
 */
# if defined(_MSC_VER)
extern  /* needed to satisfy bizarre MSVC++ interaction with inline */
# endif
inline
signed long audio_linear_dither(unsigned int bits, mad_fixed_t sample,
    struct audio_dither *dither,
    struct audio_stats *stats) {
    unsigned int scalebits;
    mad_fixed_t output, mask, random;

    enum {
        MIN                         = -MAD_F_ONE,
        MAX                         =  MAD_F_ONE - 1
    };

    /* noise shape */
    sample += dither->error[0] - dither->error[1] + dither->error[2];

    dither->error[2] = dither->error[1];
    dither->error[1] = dither->error[0] / 2;

    /* bias */
    output = sample + (1L << (MAD_F_FRACBITS + 1 - bits - 1));

    scalebits = MAD_F_FRACBITS + 1 - bits;
    mask = (1L << scalebits) - 1;

    /* dither */
    random = prng(dither->random);
    output += (random & mask) - (dither->random & mask);

    dither->random = random;

    /* clip */
    if (output >= stats->peak_sample) {
        if (output > MAX) {
            ++stats->clipped_samples;
            if (output - MAX > stats->peak_clipping) {
                stats->peak_clipping = output - MAX;
            }

            output = MAX;

            if (sample > MAX) {
                sample = MAX;
            }
        }
        stats->peak_sample = output;
    } else if (output < -stats->peak_sample) {
        if (output < MIN) {
            ++stats->clipped_samples;
            if (MIN - output > stats->peak_clipping) {
                stats->peak_clipping = MIN - output;
            }

            output = MIN;

            if (sample < MIN) {
                sample = MIN;
            }
        }
        stats->peak_sample = -output;
    }

    /* quantize */
    output &= ~mask;

    /* error feedback */
    dither->error[0] = sample - output;

    /* scale */
    return output >> scalebits;
}

/*
 * NAME:    audio_pcm_u8()
 * DESCRIPTION:    write a block of unsigned 8-bit PCM samples
 */
unsigned int audio_pcm_u8(unsigned char *data, unsigned int nsamples,
    mad_fixed_t const *left, mad_fixed_t const *right,
    enum audio_mode mode, struct audio_stats *stats) {
    unsigned int len;

    len = nsamples;

    if (right) {  /* stereo */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                data[0] = audio_linear_round(8, *left++, stats) ^ 0x80;
                data[1] = audio_linear_round(8, *right++, stats) ^ 0x80;

                data += 2;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                data[0] = audio_linear_dither(8, *left++,
                    &left_dither,  stats) ^ 0x80;
                data[1] = audio_linear_dither(8, *right++,
                    &right_dither, stats) ^ 0x80;

                data += 2;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 2;
    } else {  /* mono */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                *data++ = audio_linear_round(8, *left++, stats) ^ 0x80;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                *data++ = audio_linear_dither(8, *left++, &left_dither, stats) ^ 0x80;
            }
            break;

        default:
            return 0;
        }

        return nsamples;
    }
}

/*
 * NAME:    audio_pcm_s8()
 * DESCRIPTION:    write a block of signed 8-bit PCM samples
 */
unsigned int audio_pcm_s8(unsigned char *data, unsigned int nsamples,
    mad_fixed_t const *left, mad_fixed_t const *right,
    enum audio_mode mode, struct audio_stats *stats) {
    unsigned int len;

    len = nsamples;

    if (right) {  /* stereo */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                data[0] = audio_linear_round(8, *left++, stats);
                data[1] = audio_linear_round(8, *right++, stats);

                data += 2;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                data[0] = audio_linear_dither(8, *left++,
                    &left_dither,  stats);
                data[1] = audio_linear_dither(8, *right++,
                    &right_dither, stats);

                data += 2;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 2;
    } else {  /* mono */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                *data++ = audio_linear_round(8, *left++, stats);
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                *data++ = audio_linear_dither(8, *left++, &left_dither, stats);
            }
            break;

        default:
            return 0;
        }

        return nsamples;
    }
}

/*
 * NAME:    audio_pcm_s16le()
 * DESCRIPTION:    write a block of signed 16-bit little-endian PCM samples
 */
unsigned int audio_pcm_s16le(unsigned char *data, unsigned int nsamples,
    mad_fixed_t const *left, mad_fixed_t const *right,
    enum audio_mode mode, struct audio_stats *stats) {
    unsigned int len;
    register signed int sample0, sample1;

    len = nsamples;

    if (right) {  /* stereo */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(16, *left++, stats);
                sample1 = audio_linear_round(16, *right++, stats);

                data[0] = sample0 >> 0;
                data[1] = sample0 >> 8;
                data[2] = sample1 >> 0;
                data[3] = sample1 >> 8;

                data += 4;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(16, *left++, &left_dither, stats);
                sample1 = audio_linear_dither(16, *right++, &right_dither, stats);

                data[0] = sample0 >> 0;
                data[1] = sample0 >> 8;
                data[2] = sample1 >> 0;
                data[3] = sample1 >> 8;

                data += 4;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 2 * 2;
    } else {  /* mono */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(16, *left++, stats);

                data[0] = sample0 >> 0;
                data[1] = sample0 >> 8;

                data += 2;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(16, *left++, &left_dither, stats);

                data[0] = sample0 >> 0;
                data[1] = sample0 >> 8;

                data += 2;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 2;
    }
}

/*
 * NAME:    audio_pcm_s16be()
 * DESCRIPTION:    write a block of signed 16-bit big-endian PCM samples
 */
unsigned int audio_pcm_s16be(unsigned char *data, unsigned int nsamples,
    mad_fixed_t const *left, mad_fixed_t const *right,
    enum audio_mode mode, struct audio_stats *stats) {
    unsigned int len;
    register signed int sample0, sample1;

    len = nsamples;

    if (right) {  /* stereo */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(16, *left++, stats);
                sample1 = audio_linear_round(16, *right++, stats);

                data[0] = sample0 >> 8;
                data[1] = sample0 >> 0;
                data[2] = sample1 >> 8;
                data[3] = sample1 >> 0;

                data += 4;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(16, *left++, &left_dither, stats);
                sample1 = audio_linear_dither(16, *right++, &right_dither, stats);

                data[0] = sample0 >> 8;
                data[1] = sample0 >> 0;
                data[2] = sample1 >> 8;
                data[3] = sample1 >> 0;

                data += 4;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 2 * 2;
    } else {  /* mono */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(16, *left++, stats);

                data[0] = sample0 >> 8;
                data[1] = sample0 >> 0;

                data += 2;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(16, *left++, &left_dither, stats);

                data[0] = sample0 >> 8;
                data[1] = sample0 >> 0;

                data += 2;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 2;
    }
}

/*
 * NAME:    audio_pcm_s24le()
 * DESCRIPTION:    write a block of signed 24-bit little-endian PCM samples
 */
unsigned int audio_pcm_s24le(unsigned char *data, unsigned int nsamples,
    mad_fixed_t const *left, mad_fixed_t const *right,
    enum audio_mode mode, struct audio_stats *stats) {
    unsigned int len;
    register signed long sample0, sample1;

    len = nsamples;

    if (right) {  /* stereo */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(24, *left++, stats);
                sample1 = audio_linear_round(24, *right++, stats);

                data[0] = sample0 >> 0;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 16;

                data[3] = sample1 >> 0;
                data[4] = sample1 >> 8;
                data[5] = sample1 >> 16;

                data += 6;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(24, *left++, &left_dither, stats);
                sample1 = audio_linear_dither(24, *right++, &right_dither, stats);

                data[0] = sample0 >> 0;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 16;

                data[3] = sample1 >> 0;
                data[4] = sample1 >> 8;
                data[5] = sample1 >> 16;

                data += 6;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 3 * 2;
    } else {  /* mono */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(24, *left++, stats);

                data[0] = sample0 >> 0;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 16;

                data += 3;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(24, *left++, &left_dither, stats);

                data[0] = sample0 >> 0;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 16;

                data += 3;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 3;
    }
}

/*
 * NAME:    audio_pcm_s24be()
 * DESCRIPTION:    write a block of signed 24-bit big-endian PCM samples
 */
unsigned int audio_pcm_s24be(unsigned char *data, unsigned int nsamples,
    mad_fixed_t const *left, mad_fixed_t const *right,
    enum audio_mode mode, struct audio_stats *stats) {
    unsigned int len;
    register signed long sample0, sample1;

    len = nsamples;

    if (right) {  /* stereo */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(24, *left++, stats);
                sample1 = audio_linear_round(24, *right++, stats);

                data[0] = sample0 >> 16;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 0;

                data[3] = sample1 >> 16;
                data[4] = sample1 >> 8;
                data[5] = sample1 >> 0;

                data += 6;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(24, *left++, &left_dither, stats);
                sample1 = audio_linear_dither(24, *right++, &right_dither, stats);

                data[0] = sample0 >> 16;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 0;

                data[3] = sample1 >> 16;
                data[4] = sample1 >> 8;
                data[5] = sample1 >> 0;

                data += 6;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 3 * 2;
    } else {  /* mono */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(24, *left++, stats);

                data[0] = sample0 >> 16;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 0;

                data += 3;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample1 = audio_linear_dither(24, *left++, &left_dither, stats);

                data[0] = sample1 >> 16;
                data[1] = sample1 >> 8;
                data[2] = sample1 >> 0;

                data += 3;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 3;
    }
}

/*
 * NAME:    audio_pcm_s32le()
 * DESCRIPTION:    write a block of signed 32-bit little-endian PCM samples
 */
unsigned int audio_pcm_s32le(unsigned char *data, unsigned int nsamples,
    mad_fixed_t const *left, mad_fixed_t const *right,
    enum audio_mode mode, struct audio_stats *stats) {
    unsigned int len;
    register signed long sample0, sample1;

    len = nsamples;

    if (right) {  /* stereo */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(24, *left++, stats);
                sample1 = audio_linear_round(24, *right++, stats);

                data[0] = 0;
                data[1] = sample0 >> 0;
                data[2] = sample0 >> 8;
                data[3] = sample0 >> 16;

                data[4] = 0;
                data[5] = sample1 >> 0;
                data[6] = sample1 >> 8;
                data[7] = sample1 >> 16;

                data += 8;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(24, *left++, &left_dither, stats);
                sample1 = audio_linear_dither(24, *right++, &right_dither, stats);

                data[0] = 0;
                data[1] = sample0 >> 0;
                data[2] = sample0 >> 8;
                data[3] = sample0 >> 16;

                data[4] = 0;
                data[5] = sample1 >> 0;
                data[6] = sample1 >> 8;
                data[7] = sample1 >> 16;

                data += 8;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 4 * 2;
    } else {  /* mono */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(24, *left++, stats);

                data[0] = 0;
                data[1] = sample0 >> 0;
                data[2] = sample0 >> 8;
                data[3] = sample0 >> 16;

                data += 4;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(24, *left++, &left_dither, stats);

                data[0] = 0;
                data[1] = sample0 >> 0;
                data[2] = sample0 >> 8;
                data[3] = sample0 >> 16;

                data += 4;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 4;
    }
}

/*
 * NAME:    audio_pcm_s32be()
 * DESCRIPTION:    write a block of signed 32-bit big-endian PCM samples
 */
unsigned int audio_pcm_s32be(unsigned char *data, unsigned int nsamples,
    mad_fixed_t const *left, mad_fixed_t const *right,
    enum audio_mode mode, struct audio_stats *stats) {
    unsigned int len;
    register signed long sample0, sample1;

    len = nsamples;

    if (right) {  /* stereo */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(24, *left++, stats);
                sample1 = audio_linear_round(24, *right++, stats);

                data[0] = sample0 >> 16;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 0;
                data[3] = 0;

                data[4] = sample1 >> 16;
                data[5] = sample1 >> 8;
                data[6] = sample1 >> 0;
                data[7] = 0;

                data += 8;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(24, *left++, &left_dither, stats);
                sample1 = audio_linear_dither(24, *right++, &right_dither, stats);

                data[0] = sample0 >> 16;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 0;
                data[3] = 0;

                data[4] = sample1 >> 16;
                data[5] = sample1 >> 8;
                data[6] = sample1 >> 0;
                data[7] = 0;

                data += 8;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 4 * 2;
    } else {  /* mono */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                sample0 = audio_linear_round(24, *left++, stats);

                data[0] = sample0 >> 16;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 0;
                data[3] = 0;

                data += 4;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                sample0 = audio_linear_dither(24, *left++, &left_dither, stats);

                data[0] = sample0 >> 16;
                data[1] = sample0 >> 8;
                data[2] = sample0 >> 0;
                data[3] = 0;

                data += 4;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 4;
    }
}

static
unsigned char linear2mulaw(mad_fixed_t sample) {
    unsigned int sign, mulaw;

    enum {
        BIAS                        = (mad_fixed_t) ((0x10 << 1) + 1) << (MAD_F_FRACBITS - 13)
    };

    if (sample < 0) {
        sample = BIAS - sample;
        sign = 0x7f;
    } else {
        sample = BIAS + sample;
        sign = 0xff;
    }

    mulaw = 0x7f;
    if (sample < MAD_F_ONE) {
        unsigned int segment;
        unsigned long mask;

        segment = 7;
        for (mask = 1L << (MAD_F_FRACBITS - 1); !(sample & mask); mask >>= 1) {
            --segment;
        }

        mulaw = ((segment << 4) |
            ((sample >> (MAD_F_FRACBITS - 1 - (7 - segment) - 4)) & 0x0f));
    }

    mulaw ^= sign;

# if 0
    if (mulaw == 0x00) {
        mulaw = 0x02;
    }
# endif

    return mulaw;
}

static
mad_fixed_t mulaw2linear(unsigned char mulaw) {
    int sign, segment, mantissa, value;

    enum {
        BIAS                        = (0x10 << 1) + 1
    };

    mulaw = ~mulaw;
    sign = (mulaw >> 7) & 0x01;
    segment = (mulaw >> 4) & 0x07;
    mantissa = (mulaw >> 0) & 0x0f;

    value = ((0x21 | (mantissa << 1)) << segment) - BIAS;
    if (sign) {
        value = -value;
    }

    return (mad_fixed_t) value << (MAD_F_FRACBITS - 13);
}

/*
 * NAME:    audio_mulaw_round()
 * DESCRIPTION:    convert a linear PCM value to 8-bit ISDN mu-law
 */
inline
unsigned char audio_mulaw_round(mad_fixed_t sample, struct audio_stats *stats) {
    clip(&sample, stats);

    return linear2mulaw(sample);
}

/*
 * NAME:    audio_mulaw_dither()
 * DESCRIPTION:    convert a linear PCM value to dithered 8-bit ISDN mu-law
 */
inline
unsigned char audio_mulaw_dither(mad_fixed_t sample,
    struct audio_dither *dither,
    struct audio_stats *stats) {
    unsigned char mulaw;

    /* noise shape */
    sample += dither->error[0];

    clip(&sample, stats);

    mulaw = linear2mulaw(sample);

    /* error feedback */
    dither->error[0] = sample - mulaw2linear(mulaw);

    return mulaw;
}

/*
 * NAME:    audio_pcm_mulaw()
 * DESCRIPTION:    write a block of 8-bit mu-law encoded samples
 */
unsigned int audio_pcm_mulaw(unsigned char *data, unsigned int nsamples,
    mad_fixed_t const *left, mad_fixed_t const *right,
    enum audio_mode mode, struct audio_stats *stats) {
    unsigned int len;

    len = nsamples;

    if (right) {  /* stereo */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                data[0] = audio_mulaw_round(*left++, stats);
                data[1] = audio_mulaw_round(*right++, stats);

                data += 2;
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                data[0] = audio_mulaw_dither(*left++, &left_dither, stats);
                data[1] = audio_mulaw_dither(*right++, &right_dither, stats);

                data += 2;
            }
            break;

        default:
            return 0;
        }

        return nsamples * 2;
    } else {  /* mono */
        switch (mode) {
        case AUDIO_MODE_ROUND:
            while (len--) {
                *data++ = audio_mulaw_round(*left++, stats);
            }
            break;

        case AUDIO_MODE_DITHER:
            while (len--) {
                *data++ = audio_mulaw_dither(*left++, &left_dither, stats);
            }
            break;

        default:
            return 0;
        }

        return nsamples;
    }
}
